package com.hxwm.hwdbtool.service;

import com.hxwm.hwdbtool.entity.dbconnect.HwDateSource;
import com.hxwm.hwdbtool.util.HwFileUtil;
import com.hxwm.hwdbtool.util.HwUtil;
import com.hxwm.hwdbtool.util.JavaFXUIControlUtil;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import lombok.Data;
import org.apache.log4j.Logger;

import java.io.*;
import java.nio.charset.Charset;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * @program: hwdbtool
 * @ClassName ExecuteScriptService
 * @description:
 * @author: hxwm
 * @create: 2024-05-26 10:26
 * @Version 1.0
 **/
@Data
public class ExecuteScriptService {
    private int MAX_THREAD_NUM = 10;
    private Task<Void> backgroundTask;
    private int successNum = 0;

    private boolean isFinished = false;
    private Logger logger = Logger.getLogger(ExecuteScriptService.class);

    private ObservableList<String> logItem;//日志信息队列
    private ListView<String> logListView;//创建日志垂直列表
    private Map<String,Integer> logHashMap;

    public ExecuteScriptService(ListView<String> logListView) {
        logItem = FXCollections.observableArrayList();
        this.logListView = logListView;
        this.logListView.setItems(logItem);
    }

    /**
     * 单个脚本执行
     * @param scriptPath
     * @param outDirPath
     */
    public void executeScript(String scriptPath, String outDirPath, HwDateSource newDateSource,Charset characterSet,Button importBtn){
        if( HwUtil.isNullOrEmpty(newDateSource)|| HwUtil.isNullOrEmpty(scriptPath)){
            JavaFXUIControlUtil.showInformationDialog("提示", "脚本文件、数据源配置不能为空！");
        }
        Thread thread = new Thread(()-> {
            String substring = scriptPath.substring(scriptPath.lastIndexOf("_") + 1, scriptPath.lastIndexOf(".sql"));
            Integer countLines = Integer.valueOf(substring);
            if(HwUtil.isNullOrEmpty(countLines)){
                countLines = HwFileUtil.countLines(scriptPath);
            }
            Charset orgFileCharset = HwFileUtil.characterSetMapper(HwFileUtil.detectFileEncoding(scriptPath));
            if(countLines > 0){
                importBtn.setDisable(true);
                int successLines = 0;
                int failLines = 0;
                long startTime = System.currentTimeMillis();

                setLogMessage("A", null, "开始执行SQl脚本============================================================");

                String scriptFileNameOrg = scriptPath.substring(scriptPath.lastIndexOf("\\") + 1);
                String scriptFileName = "error_sql" + scriptFileNameOrg;

                logHashMap = new HashMap<>();

                try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(outDirPath, scriptFileName)), orgFileCharset))) {
                    try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(scriptPath), orgFileCharset))) {
                        try (Statement statement = newDateSource.getConnection().createStatement()) {
                            String line;
                            setLogMessage("A", scriptFileName, "脚本执行进度" + HwUtil.getPrintStr((successLines + failLines), countLines));
                            while ((line = reader.readLine()) != null) {
                                if (!line.trim().isEmpty()) {
                                    try {
                                        String sqlLine = new String(line.getBytes(characterSet), characterSet);
                                        if("db2".equals(newDateSource.getDriverType())){
                                            sqlLine = sqlLine.replaceAll(";$","");
                                        }
                                        statement.execute(sqlLine);
                                        successLines++;
                                    } catch (SQLException sqlException) {
                                        failLines++;
                                        bw.write(line + "\n" + sqlException.getMessage() + "\n");
                                    }
                                    setLogMessage("U", scriptFileName, "脚本执行进度：" + HwUtil.getPrintStr((successLines + failLines), countLines));
                                }
                            }
                        }
                        setLogMessage("A", null, "成功执行条数：" + successLines);
                        setLogMessage("A", null, "执行失败条数：" + failLines);
                        long endTime = System.currentTimeMillis();
                        setLogMessage("A", null, "执行耗时：" + HwUtil.transMillisToTime(endTime - startTime));
                        setLogMessage("A", null, "脚本执行结束==========================================");
                    }
                } catch (Exception e) {
                    logger.error("执行失败！" + e.getMessage());
                }finally {
                    importBtn.setDisable(false);
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }

    /**
     * 执行批量导入
     * @param inDirPath
     * @param outDirPath
     * @param newDateSource
     */
    public void executeScriptDir(String inDirPath, String outDirPath, HwDateSource newDateSource, Charset characterSet, Button importBtn){

        setLogMessage("A",null,"开始执行SQl脚本============================================================");
        long startTime = System.currentTimeMillis();
        File folder = new File(inDirPath);
        logHashMap = new HashMap<>();

        if(folder.exists() && folder.isDirectory()){
            //获得sql脚本列表
            //File[] sqlFiles = directory.listFiles((dir, name) -> name.toLowerCase().endsWith(".sql"));
            File[] files = folder.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.toLowerCase().endsWith(".sql");
                }
            });
            if(files !=null && files.length > 0){
                importBtn.setDisable(true);
                if(files.length < 10){
                    MAX_THREAD_NUM = files.length;
                }
                ExecutorService executorService = Executors.newFixedThreadPool(MAX_THREAD_NUM);
                successNum = 0;
                for (File file : files) {
                    if(file.isFile() && file.getName().endsWith(".sql")){
                        String scriptPath = file.getAbsolutePath();
                        String scriptFileNameOrg = scriptPath.substring(scriptPath.lastIndexOf("\\") + 1);

                        //执行线程
                        backgroundTask = createSqlTask(scriptPath,outDirPath,newDateSource,characterSet);
                        //监听任务updateMessage() 信息变化
                        backgroundTask.messageProperty().addListener((observable,oldValue,newValue)->{
                            if(newValue!=null){
                                String[] split = newValue.split("--");
                                setLogMessage(split[0],split[1],split[1] +":" +split[2]);
                            }
                        });
                        //监听任务是否执行完毕
                        backgroundTask.setOnSucceeded(workerStateEvent->{
                            successNum ++;
                            logger.info(scriptFileNameOrg + "==脚本执行成功!");
                            if(successNum == files.length){
                                long endTime = System.currentTimeMillis();
                                setLogMessage("A",null,"执行耗时："+ HwUtil.transMillisToTime(endTime-startTime));
                                setLogMessage("A",null,"脚本执行结束============================================");
                                importBtn.setDisable(false);
                            }
                        });
                        executorService.submit(backgroundTask);
                    }
                }
                executorService.shutdown();
            }
        }
    }

    /**
     * 创建数据库执行的任务
     * @param scriptPath
     * @param outDirPath
     * @param newDateSource
     * @return
     */
    private Task<Void> createSqlTask(String scriptPath, String outDirPath,HwDateSource newDateSource,Charset characterSet) {
        return new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                Charset orgFileCharset = HwFileUtil.characterSetMapper(HwFileUtil.detectFileEncoding(scriptPath));
                String lineNum = scriptPath.substring(scriptPath.lastIndexOf("_") + 1, scriptPath.lastIndexOf(".sql"));
                Integer countLines = Integer.valueOf(lineNum);
                if(HwUtil.isNullOrEmpty(countLines)) {
                    countLines = HwFileUtil.countLines(scriptPath);
                }

                if(countLines > 0){
                    int successLines = 0;
                    int failLines = 0;
                    String scriptFileNameOrg = scriptPath.substring(scriptPath.lastIndexOf("\\") + 1);
                    setLogMessage("A",scriptFileNameOrg,"脚本：" + scriptFileNameOrg + "开始执行");
                    String scriptFileName = "errorSql_" + scriptFileNameOrg;

                    try(BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(outDirPath, scriptFileName)), characterSet))){
                        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(scriptPath),orgFileCharset))){
                            try(Statement statement = newDateSource.getConnection().createStatement()){
                                String line;
                                while((line = reader.readLine()) != null){
                                    if(!line.trim().isEmpty()){
                                        try{
                                            String sqlLine = new String(line.getBytes(characterSet),characterSet);
                                            if("db2".equals(newDateSource.getDriverType())){
                                                sqlLine = sqlLine.replaceAll(";$","");
                                            }
                                            statement.execute(sqlLine);
                                            successLines++;
                                        }catch (SQLException sqlException){
                                            failLines++;
                                            bw.write(line + "\n" + sqlException.getMessage() + "\n");
                                        }
                                        updateMessage("U--" + scriptFileNameOrg + "--执行进度" + HwUtil.getPrintStr((successLines+ failLines),countLines));
                                        /**updateProgress((successLines + failLines), countLines);
                                        Double process = ((double) (successLines + failLines) / countLines) * 100;
                                        updateMessage(scriptFileNameOrg + "--执行进度--" + process + "%");**/
                                    }
                                }
                            }
                        }
                    }
                }
                return null;
            }
        };
    }
    /**
     * 日志实时打印
     * @param operType
     * @param key
     * @param message
     */
    private void setLogMessage(String operType,String key,String message){
        //U-更新 A-追加
        //JFX的UI更新操作需要放在APP的主线程操作
        Platform.runLater(()->{
            if("A".equals(operType)){
                logItem.add(message);
                if(key != null){
                    logHashMap.put(key,logItem.size()-1);
                }
            }else{
                logItem.set(logHashMap.get(key),message);
            }
        });
    }
}